package net.sf.fc.script;

import java.io.File;
import java.io.IOException;
import java.net.URL;

import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

import org.w3c.dom.Node;
import org.xml.sax.SAXException;

public final class ScriptHelper<T> implements ScriptMarshaller<T>,
        ScriptUnmarshaller<T>, ScriptValidator {

    // Variables for ScriptValidator implementation
    private final Schema schema;
    private final Validator validator;

    // Variable for ScriptMarshaller implementation
    private final Marshaller marshaller;

    // Variable for ScriptUnmarshaller implementation
    private final Unmarshaller unmarshaller;

    private final Class<T> clazz;

    private final SchemaData schemaData;

    protected ScriptHelper(Class<T> clazz, SchemaData schemaData) throws SAXException, JAXBException {
        this.clazz = clazz;
        this.schemaData = schemaData;
        schema = initSchema(schemaData.xsdName());
        validator = schema.newValidator();
        marshaller = initMarshaller(schemaData.contextPath());
        unmarshaller = initUnmarshaller(schemaData.contextPath());
    }

    public static <C> ScriptHelper<C> createScriptHelper(Class<C> clazz, SchemaData schemaData)
            throws SAXException, JAXBException {
        return new ScriptHelper<C>(clazz, schemaData);
    }

    // Init method for ScriptValidator implementation
    private Schema initSchema(String xsdName) throws SAXException {
        SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
        return sf.newSchema(ClassLoader.getSystemResource(xsdName));
    }

    // Init method for ScriptMarshaller implementation
    private Marshaller initMarshaller(String contextPath) throws JAXBException {
        JAXBContext jc = JAXBContext.newInstance(contextPath);
        Marshaller m = jc.createMarshaller();
        m.setSchema(schema);
        m.setProperty("jaxb.formatted.output", Boolean.TRUE);
        return m;
    }

    // Init method for ScriptUnmarshaller implementation
    private Unmarshaller initUnmarshaller(String contextPath) throws SAXException, JAXBException {
        JAXBContext jc = JAXBContext.newInstance(contextPath);
        Unmarshaller u = jc.createUnmarshaller();
        u.setSchema(schema);
        return u;
    }

    // ScriptValidator implementation. Throws SAXException if script is not valid.
    @Override
    public void validate(File xmlFile) throws IOException, SAXException {
        validator.validate(new StreamSource(xmlFile));
    }

    // ScriptMarshaller implementation
    @Override
    public void marshal(File file, T t) throws JAXBException {
        marshaller.marshal(new JAXBElement<T>(schemaData.qname(), clazz, t),
                file);
    }

    @Override
    public void marshal(Node node, T t) throws JAXBException {
        marshaller.marshal(new JAXBElement<T>(schemaData.qname(), clazz, t), node);
    }

    // ScriptUnmarshaller implementation
    @Override
    public T unmarshal(File file) throws JAXBException {
        @SuppressWarnings("unchecked")
        JAXBElement<T> doc = (JAXBElement<T>)unmarshaller.unmarshal(file);
        return doc.getValue();
    }

    @Override
    public T unmarshal(URL url) throws JAXBException {
        @SuppressWarnings("unchecked")
        JAXBElement<T> doc = (JAXBElement<T>)unmarshaller.unmarshal(url);
        return doc.getValue();
    }

    @Override
    public T unmarshal(Node node) throws JAXBException {
        @SuppressWarnings("unchecked")
        JAXBElement<T> doc = (JAXBElement<T>)unmarshaller.unmarshal(node);
        return doc.getValue();
    }
}
